
#include "uartcom.h"

#include <termios.h>

//STATIC OR GLOBLE INITIAL
UartCom *UartCom::mpInstance = NULL;

//FUNCTION
//TinyFrame Start===================================================================
/**
 * This is an example of integrating TinyFrame into the application.
 * 
 * TF_WriteImpl() is required, the mutex functions are weak and can
 * be removed if not used. They are called from all TF_Send/Respond functions.
 * 
 * Also remember to periodically call TF_Tick() if you wish to use the 
 * listener timeout feature.
 */
//Global VAR
LTGUI_MUTEX *g_TFTxMutex = LTGUI_CREATE_LOCK();

void TF_WriteImpl(TinyFrame *tf, const uint8_t *buff, uint32_t len)
{
    // send to UART
    UartCom::GetInstance()->UartSend((CHAR *)buff, len);
}

// --------- Mutex callbacks ----------
// Needed only if TF_USE_MUTEX is 1 in the config file.
// DELETE if mutex is not used

/** Claim the TX interface before composing and sending a frame */
bool TF_ClaimTx(TinyFrame *tf)
{
    // take mutex
    LTGUI_LOCK(g_TFTxMutex);
    return true; // we succeeded
}

/** Free the TX interface after composing and sending a frame */
void TF_ReleaseTx(TinyFrame *tf)
{
    // release mutex
    LTGUI_UNLOCK(g_TFTxMutex);
}

// --------- Custom checksums ---------
// This should be defined here only if a custom checksum type is used.
// DELETE those if you use one of the built-in checksum types

/** Initialize a checksum */
// TF_CKSUM TF_CksumStart(void)
// {
// return 0;
// }

// /** Update a checksum with a byte */
// TF_CKSUM TF_CksumAdd(TF_CKSUM cksum, uint8_t byte)
// {
// return cksum ^ byte;
// }

// /** Finalize the checksum calculation */
// TF_CKSUM TF_CksumEnd(TF_CKSUM cksum)
// {
// return cksum;
// }
//TinyFrame End===================================================================

UartCom *UartCom::GetInstance(LPCTSTR pUartPath, INT nSpeed, INT nBits, CHAR nEvent, INT nStop)
{
    if (mpInstance == NULL)
    {
        mpInstance = new UartCom(pUartPath, nSpeed, nBits, nEvent, nStop);
    }

    return mpInstance;
}

UartCom::UartCom(LPCTSTR pUartPath, INT iSpeed, INT iBits, CHAR cEvent, INT iStop) : mpTf(NULL), mUartFd(INVALID_VALUE), mpJobTh(NULL)
{
    if (pUartPath == NULL){
        CLog::LogU("[%s] Invalid UartPath\n", __FUNCTION__);
        return;
    }

    //Init tinyfram potocol
    mpTf = TF_Init(TF_SLAVE);

    if ((mUartFd = open(pUartPath, O_RDWR | O_NOCTTY | O_NDELAY)) < 0)
    {
        CLog::LogU("[%s] Open %s is failed\n",__FUNCTION__, pUartPath);
    }
    else
    {
        //Setting Uart
        SetUartOpt(iSpeed, iBits, cEvent, iStop);
    }

    mpJobTh = LTGUI_CREATE_THREAD(UartCom::OnUartJobProc, this);
    CLog::LogU("[%s] Open UART %s %d-%d%c%d is OK\n", __FUNCTION__, pUartPath, iSpeed, iBits, cEvent, iStop);
}

UartCom::~UartCom()
{
    if (mUartFd != -1)
    {
        close(mUartFd);
    }
}

BOOL UartCom::UartCom_Send(UC_MSG *msg)
{
    LT_CHECK_RET(mpTf != NULL && msg != NULL, FALSE);
    return TF_Send(mpTf, msg);
}

BOOL UartCom::UartCom_SendSimple(INT iType, CHAR *pData, INT iLen)
{
    LT_CHECK_RET(mpTf != NULL, FALSE);

    return TF_SendSimple(mpTf, iType, (const uint8_t *)pData, iLen);
}

BOOL UartCom::UartCom_Query(UC_MSG *msg, UC_LISTENER listener, INT timeout)
{
    LT_CHECK_RET(mpTf != NULL && listener != NULL, FALSE);

    return TF_Query(mpTf, msg, listener, timeout);
}

BOOL UartCom::UartCom_QuerySimple(INT iType, CHAR *pData, INT iLen, UC_LISTENER listener, UC_TICKS timeout)
{
    LT_CHECK_RET(mpTf != NULL, FALSE);

    return TF_QuerySimple(mpTf, iType, (const uint8_t *)pData, iLen, listener, timeout);
}

BOOL UartCom::UartCom_Respond(UC_MSG *msg)
{
    LT_CHECK_RET(mpTf != NULL && msg != NULL, FALSE);

    return TF_Respond(mpTf, msg);
}

BOOL UartCom::UartCom_AddGenericListener(UC_LISTENER cb)
{
    LT_CHECK_RET(mpTf != NULL, FALSE);

    return TF_AddGenericListener(mpTf, cb);
}

BOOL UartCom::UartCom_RemoveGenericListener(UC_LISTENER cb)
{
    LT_CHECK_RET(mpTf != NULL, FALSE);

    return TF_RemoveGenericListener(mpTf, cb);
}

BOOL UartCom::UartCom_AddTypeListener(INT iType, UC_LISTENER cb)
{
    LT_CHECK_RET(mpTf != NULL, FALSE);

    return TF_AddTypeListener(mpTf, iType, cb);
}

BOOL UartCom::UartCom_RemoveTypeListener(INT iType)
{
    LT_CHECK_RET(mpTf != NULL, FALSE);

    return TF_RemoveTypeListener(mpTf, iType);
}

BOOL UartCom::UartCom_AddIdListener(UC_MSG *msg, UC_LISTENER cb, UC_TICKS timeout)
{
    LT_CHECK_RET(mpTf != NULL, FALSE);

    return TF_AddIdListener(mpTf, msg, cb, timeout);
}

BOOL UartCom::UartCom_RemoveIdListener(UC_ID frame_id)
{
    LT_CHECK_RET(mpTf != NULL, FALSE);

    return TF_RemoveIdListener(mpTf, frame_id);
}

INT UartCom::OnUartJobProc(VOID *pParam)
{
    #define BUFLEN_PER_READ 1

    UartCom *puartcom = reinterpret_cast<UartCom *>(pParam);
    INT irealcnt = 0;
    CHAR tmpbuf[BUFLEN_PER_READ] = {0};

    LT_CHECK_RET(pParam, ERR_INVALID);
    LTGUI_SET_THREADNAME("uartcom");

    //do user job proc
    while (TRUE)
    {
        irealcnt = puartcom->UartRecvTimeout(tmpbuf, BUFLEN_PER_READ, 100);
        if (irealcnt > 0){
            puartcom->PushBuffToTf(tmpbuf, irealcnt);
        }
        
        //CLog::LogU("[%s] irealcnt:%d \n", __FUNCTION__, irealcnt);
    }

    return ERR_OK;
}

INT UartCom::PushBuffToTf(CHAR *pBuf, INT iLen)
{
    //check uart fd value?
    LT_CHECK_RET(mpTf != NULL && pBuf != NULL && iLen >0, ERR_INVALID);

    TF_Accept(mpTf, (const uint8_t*)pBuf, (size_t)iLen);
    return ERR_OK;
}

INT UartCom::SetUartOpt(INT iSpeed, INT iBits, CHAR cEvent, INT iStop)
{
    struct termios newtio, oldtio;

    //check uart fd value?
    LT_CHECK_RET(mUartFd >= 0, ERR_INVALID);

    /*获取原有串口配置*/
    if (tcgetattr(mUartFd, &oldtio) != 0)
    {
        CLog::LogU("[%s] Getting UART Error\n", __FUNCTION__);
        return ERR_INVALID;
    }

    memset(&newtio, 0, sizeof(newtio));
    /*CREAD 开启串行数据接收，CLOCAL并打开本地连接模式*/
    newtio.c_cflag |= CLOCAL | CREAD;

    /*设置数据位*/
    newtio.c_cflag &= ~CSIZE;
    switch (iBits)
    {
    case 7:
        newtio.c_cflag |= CS7;
        break;
    case 8:
        newtio.c_cflag |= CS8;
        break;
    }
    /* 设置奇偶校验位 */
    switch (cEvent)
    {
    case 'O':
        newtio.c_cflag |= PARENB;
        newtio.c_cflag |= PARODD;
        newtio.c_iflag |= (INPCK | ISTRIP);
        break;
    case 'E':
        newtio.c_iflag |= (INPCK | ISTRIP);
        newtio.c_cflag |= PARENB;
        newtio.c_cflag &= ~PARODD;
        break;
    case 'N':
        newtio.c_cflag &= ~PARENB;
        break;
    }
    /* 设置波特率 */
    switch (iSpeed)
    {
    case 2400:
        cfsetispeed(&newtio, B2400);
        cfsetospeed(&newtio, B2400);
        break;
    case 4800:
        cfsetispeed(&newtio, B4800);
        cfsetospeed(&newtio, B4800);
        break;
    case 9600:
        cfsetispeed(&newtio, B9600);
        cfsetospeed(&newtio, B9600);
        break;
    case 115200:
        cfsetispeed(&newtio, B115200);
        cfsetospeed(&newtio, B115200);
        break;
    case 460800:
        cfsetispeed(&newtio, B460800);
        cfsetospeed(&newtio, B460800);
        break;
    default:
        cfsetispeed(&newtio, B9600);
        cfsetospeed(&newtio, B9600);
        break;
    }

    /*设置停止位*/
    if (iStop == 1)                /*设置停止位；若停止位为1，则清除CSTOPB，若停止位为2，则激活CSTOPB*/
        newtio.c_cflag &= ~CSTOPB; /*默认为一位停止位； */
    else if (iStop == 2)
        newtio.c_cflag |= CSTOPB;

    /*设置最少字符和等待时间，对于接收字符和等待时间没有特别的要求时*/
    newtio.c_cc[VTIME] = 0; /*非规范模式读取时的超时时间；*/
    newtio.c_cc[VMIN] = 0;  /*非规范模式读取时的最小字符数*/

    /*tcflush清空终端未完成的输入/输出请求及数据；TCIFLUSH表示清空正收到的数据，且不读取出来 */
    tcflush(mUartFd, TCIFLUSH);
    if ((tcsetattr(mUartFd, TCSANOW, &newtio)) != 0)
    {
        CLog::LogU("[%s] Setting UART Error\n", __FUNCTION__);
        return ERR_INVALID;
    }

    //CLog::LogU("[%s] Setting UART %d-%d%c%d is OK\n", __FUNCTION__, iSpeed, iBits, cEvent, iStop);
    return ERR_OK;
}

INT UartCom::UartSend(CHAR *pBuf, INT iLen)
{
    INT ret = 0;
    INT count = 0;

    //check uart fd value?
    LT_CHECK_RET((mUartFd >= 0) && (pBuf != NULL) && (iLen > 0), ERR_INVALID);

    tcflush(mUartFd, TCIFLUSH);
    while (iLen > 0)
    {
        ret = write(mUartFd, (CHAR *)pBuf + count, iLen);
        if (ret < 1)
        {
            break;
        }
        count += ret;
        iLen = iLen - ret;
    }

    return count;
}

INT UartCom::UartRecvTimeout(CHAR *pBuf, INT iLen, INT iTimeoutMs)
{
    INT ret;
    size_t rsum = 0;
    ret = 0;
    fd_set rset;
    struct timeval t;

    //check uart fd value?
    LT_CHECK_RET(mUartFd >= 0, ERR_INVALID);

    while (rsum < iLen)
    {
        t.tv_sec = iTimeoutMs / 1000;
        t.tv_usec = (iTimeoutMs - t.tv_sec * 1000) * 1000;
        FD_ZERO(&rset);
        FD_SET(mUartFd, &rset);
        ret = select(mUartFd + 1, &rset, NULL, NULL, &t);
        if (ret <= 0)
        {
            if (ret == 0)
            {
                //timeout
                return -1;
            }
            if (errno == EINTR)
            {
                // 信号中断
                continue;
            }
            return -errno;
        }
        else
        {
            ret = read(mUartFd, (char *)pBuf + rsum, iLen - rsum);
            if (ret < 0)
            {
                return ret;
            }
            else
            {
                rsum += ret;
            }
        }
    }

    return rsum;
}

VOID UartCom::UartCom_DumpFrameInfo(UC_MSG *msg)
{
    printf("\033[33mFrame info\n"
           "  type: %02Xh\n"
           "  data: \"%.*s\"\n"
           "   len: %u\n"
           "    id: %Xh\033[0m\n\n",
           msg->type, msg->len, msg->data, msg->len, msg->frame_id);
}